namespace cxxcall
{

void av_data::_Reg_struct_ret ()
{
#ifndef _WIN32
  if (this->rsize <= 16)
    this->flags |= reg_struct_ret;
  else
    this->iargs[this->ianum++] = (uint64_t)(unsigned long)this->raddr;
#else
  switch (this->rsize)
    {
      case 1: case 2: case 4: case 8:
        this->flags |= reg_struct_ret;
        break;
      default:
        *this->curp++ = (uintptr_t)this->raddr;
        ++this->anum;
    }
#endif
}

bool av_data::arg_t (intptr_t val)
{
#ifndef _WIN32
  if (this->ianum < IARG_NUM)
    {
      this->iargs[this->ianum++] = val;
      return (true);
    }
  else
#endif
  if (this->curp >= this->endp)
    return (this->set_overflow ());

  *this->curp++ = val;
#ifdef _WIN32
  ++this->anum;
  this->fnum += (this->flags & variadic_fn) / variadic_f;
#endif

  return (true);
}

#ifndef _WIN32

bool av_data::arg (float val)
{
  if (this->fptr < &this->fargs[FARG_NUM])
    memcpy (this->fptr++, &val, sizeof (val));
  else if (this->curp >= this->endp)
    return (this->set_overflow ());
  else
    memcpy (this->curp++, &val, sizeof (val));

  return (true);
}

bool av_data::arg (double val)
{
  if (this->fptr < &this->fargs[FARG_NUM])
    *this->fptr++ = val;
  else if (this->curp >= this->endp)
    return (this->set_overflow ());
  else
    memcpy (this->curp++, &val, sizeof (val));

  return (true);
}

#ifndef CXXCALL_LONG_DOUBLE_ALIASED

bool av_data::arg (long double val)
{
  if (this->curp + 1 >= this->endp)
    return (this->set_overflow ());

  memcpy (this->curp, &val, sizeof (val));
  this->curp += 2;
  return (true);
}

#endif

bool av_data::arg (const void *ptr, size_t size, size_t align)
{
  if (size <= 2 * WSIZE && this->ianum +
      (size - WSIZE - 1) / WSIZE <= IARG_NUM)
    {
      memcpy (&this->iargs[this->ianum], ptr, size);
      this->ianum += (size + WSIZE - 1) / WSIZE;
    }
  else
    {
      auto ep = (uintptr_t *)((uintptr_t)this->curp +
        ((size + align - 1) & -(intptr_t)align));

      if (ep >= this->endp)
        return (this->set_overflow ());

      memcpy (this->curp, ptr, size);
      this->curp = ep;
    }

  return (true);
}

#else

template <class T>
static bool av_data_flt_arg (av_data& self, T val)
{
  // XXX: varargs functions may used a different convention.
  if (self.fnum < av_data::FARG_NUM)
    {
      self.fargs[self.fnum++] = val;
      if (self.flags & variadic_fn)
        {
          ++self.anum;
          goto do_push;
        }
    }
  else
    {
    do_push:
      if (self.curp >= self.endp)
        return (self.set_overflow ());

      memcpy (self.curp++, &val, sizeof (val));
    }

  return (true);
}

bool av_data::arg (float val)
{
  return (av_data_flt_arg<float> (*this, val));
}

bool av_data::arg (double val)
{
  return (av_data_flt_arg<double> (*this, val));
}

#ifndef CXXCALL_LONG_DOUBLE_ALIASED

bool av_data::arg (long double val)
{
  if (this->curp + sizeof (val) / WSIZE - 1 >= this->endp)
    return (this->set_overflow ());

  memcpy (this->curp, &val, sizeof (val));
  this->curp += sizeof (val) / WSIZE;
  return (true);
}

#endif

bool av_data::arg (const void *ptr, size_t size, size_t align)
{
  uintptr_t *ep;

  switch (size)
    {
      case 1: case 2: case 4: case 8:
        ep = (uintptr_t *)((uintptr_t)this->aptr +
          ((size + WSIZE - 1) & -(intptr_t)WSIZE));
        if (ep >= this->endp)
          return (this->set_overflow ());
        memcpy (this->curp, ptr, size);
        break;

      default:
        ep = (uintptr_t *)((uintptr_t)this->endp - ((size + 7) & -8));
        if (this->curp + 1 > ep)
          return (this->set_overflow ());

        memcpy (this->endp = ep, val, size);
        *this->curp++ = (uintptr_t)ep;
    }

  return (true);
}

#endif

} // namespace cxxcall
